SpringAsync异步编程

Spring为任务调度与异步方法执行提供了注解支持。通过在方法上设置@Async注解,可使得方法被异步调用。也就是说调用者会在调用时立即返回,而被调用方法的实际执行是交给Spring的TaskExecutor来完成。

Async示例

定义接口

1
2
3
4
5
6
public interface IDemoService {

public String syncServiceInvoke() throws Exception;

public String asynServiceInvoke() throws Exception;
}

接口实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Service
public class IDemoServiceImpl implements IDemoService{

// 异步操作必须和被调用的线程独立开
@Resource
private AsynDemoServiceProxy asynDemoServiceProxy;

public String syncServiceInvoke() throws Exception {
long start = System.currentTimeMillis();
String str1 = asynDemoServiceProxy.asynMethod().get();
String str = syncMethod();
String str2 = asynDemoServiceProxy.asynMethod().get();
return "同步调用: 方法1:" + str1 + "方法2:" + str + "方法3:" + str2 + ",总共耗时:" + (System.currentTimeMillis() - start) + "ms";
}

public String asynServiceInvoke() throws Exception {
long start = System.currentTimeMillis();
Future<String> str1 = asynDemoServiceProxy.asynMethod();
String str = syncMethod();
Future<String> str2 = asynDemoServiceProxy.asynMethod();
return "异步调用: 方法1:" + str1.get() + "方法2:" + str + "方法3:" + str2.get() + ",总共耗时:" + (System.currentTimeMillis() - start) + "ms";
}

public String syncMethod(){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "耗时:2s";
}

}

示例中分别实现了两个方法: 一个同步调用, 一个异步调用(AsynDemoServiceProxy类中定义了方法的异步实现,这里在并行结果等待场景下,异步逻辑不能和当前主线程处于同一线程中, 负责异步会失效), 代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
@EnableAsync
public class AsynDemoServiceProxy {

@Async
public Future<String> asynMethod(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new AsyncResult<>("耗时:3s");
}

}

RestAPI定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Controller
@RequestMapping(value = "/api/demo")
public class AsyncDemoController extends BaseMultiController {

private static final Logger LOG = LoggerFactory.getLogger(LeopardDemoController.class);

@ResponseBody
@RequestMapping(value = "/invoke/sync", method = RequestMethod.GET)
public APIResult sync() throws Exception{
return new APIResult(ResultEnum.SUCCESS, demoService.syncServiceInvoke());
}

@ResponseBody
@RequestMapping(value = "/invoke/asyn", method = RequestMethod.GET)
public APIResult asyn() throws Exception{
return new APIResult(ResultEnum.SUCCESS, demoService.asynServiceInvoke());
}
}

开启@Async支持

SpringMvc处理器xml中配置如下内容

1
2
3
<!-- 支持 @Async 注解 -->
<task:annotation-driven executor="myExecutor"/>
<task:executor id="myExecutor" pool-size="5-20" queue-capacity="100"/>

示例测试

#### 同步调用测试

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void sync() throws Exception {
String uri = "/api/demo/invoke/sync";
MvcResult mvcResult = this.mockMvc.perform(MockMvcRequestBuilders.get(uri)
.cookie(new Cookie("token", "61ADD4B8ED86C7E162"))
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8))
.andExpect(handler().handlerType(AsyncDemoController.class))
.andExpect(status().isOk())
.andDo(print())
.andReturn();
println(mvcResult.getResponse().getContentAsString());
}

程序执行结果(约8ms):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
MockHttpServletRequest:
HTTP Method = GET
Request URI = /api/demo/invoke/sync
Parameters = {}
Headers = {Content-Type=[application/json;charset=UTF-8], Accept=[application/json;charset=UTF-8]}

Handler:
Type = com.tutorial.spring.async.api.web.controller.AsyncDemoController
Method = public com.tutorial.spring.async.api.result.APIResult com.tutorial.spring.async.api.web.controller.AsyncDemoController.sync() throws java.lang.Exception

Async:
Async started = false
Async result = null

Resolved Exception:
Type = null

ModelAndView:
View name = null
View = null
Model = null

FlashMap:
Attributes = null

MockHttpServletResponse:
Status = 200
Error message = null
Headers = {M-Appkey=[com.tutorial.spring.async.api], M-SpanName=[AsyncDemoController.sync], M-Host=[172.30.12.197], Content-Type=[application/json;charset=UTF-8], Content-Length=[126]}
Content type = application/json;charset=UTF-8
Body = {"data":"同步调用: 方法1:耗时:3s方法2:耗时:2s方法3:耗时:3s,总共耗时:8019ms","message":"成功","status":0}
Forwarded URL = null
Redirected URL = null
Cookies = []
{"data":"同步调用: 方法1:耗时:3s方法2:耗时:2s方法3:耗时:3s,总共耗时:8019ms","message":"成功","status":0}

#### 异步调用测试

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void async() throws Exception {
String uri = "/api/demo/invoke/asyn";
MvcResult mvcResult = this.mockMvc.perform(MockMvcRequestBuilders.get(uri, 1, 1, 15)
.cookie(new Cookie("token", "61ADD4B8ED86C7E162"))
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8))
.andExpect(handler().handlerType(AsyncDemoController.class))
.andExpect(status().isOk())
.andDo(print())
.andReturn();
println(mvcResult.getResponse().getContentAsString());
}

程序执行结果(约5ms):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
MockHttpServletRequest:
HTTP Method = GET
Request URI = /api/demo/invoke/asyn
Parameters = {}
Headers = {Content-Type=[application/json;charset=UTF-8], Accept=[application/json;charset=UTF-8]}

Handler:
Type = com.tutorial.spring.async.api.web.controller.AsyncDemoController
Method = public com.tutorial.spring.async.api.result.APIResult com.tutorial.spring.async.api.web.controller.AsyncDemoController.asyn() throws java.lang.Exception

Async:
Async started = false
Async result = null

Resolved Exception:
Type = null

ModelAndView:
View name = null
View = null
Model = null

FlashMap:
Attributes = null

MockHttpServletResponse:
Status = 200
Error message = null
Headers = {M-Appkey=[com.tutorial.spring.async.api], M-SpanName=[AsyncDemoController.asyn], M-Host=[172.30.12.197], Content-Type=[application/json;charset=UTF-8], Content-Length=[126]}
Content type = application/json;charset=UTF-8
Body = {"data":"异步调用: 方法1:耗时:3s方法2:耗时:2s方法3:耗时:3s,总共耗时:5011ms","message":"成功","status":0}
Forwarded URL = null
Redirected URL = null
Cookies = []
{"data":"异步调用: 方法1:耗时:3s方法2:耗时:2s方法3:耗时:3s,总共耗时:5011ms","message":"成功","status":0}

可以明显的看到, 其中串行的一个3ms的耗时被优化.相比于我们自己去定义一个线程体, 然后调用,这样的方式更优雅简单,也利于维护和调整.

问题解决

同一Bean中,异步等待场景,调用失效